/*
Pyxis 2.0
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections;

using Microsoft.SPOT;
using Microsoft.SPOT.Presentation.Media;

using Skewworks.Pyxis.EXT;
using Skewworks.Pyxis.Kernel;

namespace Skewworks.Pyxis.GUI.Controls
{
    public class ContextMenu : Control
    {

        #region Variables

        private PyxisAPI API = null;

        private ArrayList _MenuItems = new ArrayList();

        private Color SelBottom = ColorUtility.ColorFromRGB(184, 214, 248);
        private Color NrmlTop = ColorUtility.ColorFromRGB(241, 241, 241);
        private Color DrkGray = ColorUtility.ColorFromRGB(112, 112, 112);

        private ContextMenuItem _selMenu;

        #endregion

        #region Constructors

        public ContextMenu()
        {
            _visible = false;
        }

        public ContextMenu(ContextMenuItem[] MenuItems)
        {
            for (int i = 0; i < MenuItems.Length; i++)
                _MenuItems.Add(MenuItems[i]);

            _visible = false;
        }

        public ContextMenu(string[] MenuItems)
        {
            for (int i = 0; i < MenuItems.Length; i++)
                _MenuItems.Add(new ContextMenuItem(MenuItems[i]));
        }

        #endregion

        #region Properties

        /// <summary>
        /// Returns a specific menu item
        /// </summary>
        /// <param name="index">Index of menu item to get</param>
        /// <returns></returns>
        public ContextMenuItem Item(int index)
        {
            return (ContextMenuItem)_MenuItems[index];
        }

        /// <summary>
        /// Get's the number of items
        /// </summary>
        public int ItemCount
        {
            get { return _MenuItems.Count; }
        }

        public override bool Visible
        {
            get
            {
                return base.Visible;
            }
            set
            {
                throw new Exception("Cannot set visibility of ContextMenus");
            }
        }

        #endregion

        #region GUI

        /// <summary>
        /// Draws a collection of submenu items
        /// </summary>
        /// <param name="mnu">Top-level MenuItem</param>
        /// <param name="x">Draw destination X</param>
        /// <param name="y">Draw destination Y</param>
        private void drawExpandedMenu(ContextMenuItem mnu, int x, int y, bool setBounds = false)
        {
            ContextMenuItem m;
            int i;
            int offsetX = (menuHasIcons(mnu)) ? 20 : 0;
            rect rmnu = getExpandedMenuRect(mnu, x, y);
            x = rmnu.X;
            y = rmnu.Y;
            if (setBounds)
            {
                _w = rmnu.Width;
                _h = rmnu.Height;
                _x = x;
                _y = y;
            }

            int exW, exH;
            FontManager.Arial.ComputeExtent(">", out exW, out exH);

            // Draw the outline of the menu
            API.ScreenBuffer.DrawRectangle(DrkGray, 1, x, y, rmnu.Width, rmnu.Height, 0, 0, NrmlTop, 0, 0, NrmlTop, 0, 0, 256);
            API.ScreenBuffer.DrawRectangle(Color.White, 1, x + 1, y + 1, rmnu.Width - 2, rmnu.Height - 2, 0, 0, Color.White, 0, 0, Color.White, 0, 0, 0);

            // Draw each text item
            x = x + 4;
            y = y + 2;
            for (i = 0; i < mnu.ItemCount; i++)
            {
                m = mnu.Item(i);

                if (m.Visible)
                {
                    if (m.Text == "-")
                    {
                        API.ScreenBuffer.DrawLine(Color.White, 1, x, y + 2, x + rmnu.Width - 8, y + 2);
                        API.ScreenBuffer.DrawLine(DrkGray, 1, x, y + 3, x + rmnu.Width - 8, y + 3);

                    }
                    else
                    {
                        if (m == _selMenu)
                        {
                            API.ScreenBuffer.DrawRectangle(Color.White, 0, x - 2, y, rmnu.Width - 4, m.Height, 0, 0, SelBottom, 0, 0, SelBottom, 0, 0, 256);
                            API.ScreenBuffer.DrawText(m.Text, FontManager.Arial, Color.White, x + offsetX, y + 2);
                            m.DrawIcon(API.ScreenBuffer, x, y);
                            if (m.ItemCount > 0) API.ScreenBuffer.DrawText(">", FontManager.Arial, Color.White, x + rmnu.Width - exW - 8, y + 2);
                        }
                        else
                        {
                            API.ScreenBuffer.DrawText(m.Text, FontManager.Arial, (m.Enabled) ? Color.Black : Colors.Gray, x + offsetX, y + 2);
                            m.DrawIcon(API.ScreenBuffer, x, y);
                            if (m.ItemCount > 0) API.ScreenBuffer.DrawText(">", FontManager.Arial, (m.Enabled) ? Color.Black : Colors.Gray, x + rmnu.Width - exW - 8, y + 2);
                        }
                        m.Bounds = new rect(x, y, rmnu.Width - 8, m.Height);
                    }
                    y += m.Height;


                    if (m.Expanded)
                    {
                        drawExpandedMenu(m, x + rmnu.Width - 5, y - m.Height - 2);
                    }
                }
            }

            API.ScreenBuffer.Flush(rmnu.X, rmnu.Y, rmnu.Width, rmnu.Height);

        }

        private void RenderMenuItem(ContextMenuItem mnu)
        {
            rect Bounds = mnu.Bounds;

            if (!mnu.Visible) return;

            API.ScreenBuffer.SetClippingRectangle(0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);

                int offsetX = (menuHasIcons(mnu.Parent)) ? 20 : 0;
                int exW, exH;
                FontManager.Arial.ComputeExtent(">", out exW, out exH);

                if (mnu == _selMenu)
                {
                    API.ScreenBuffer.DrawRectangle(SelBottom, 0, Bounds.X - 2, Bounds.Y, Bounds.Width + 4, mnu.Height, 0, 0, SelBottom, 0, 0, SelBottom, 0, 0, Bitmap.OpacityOpaque);
                    API.ScreenBuffer.DrawText(mnu.Text, FontManager.Arial, Color.White, Bounds.X + offsetX, Bounds.Y + 2);
                    mnu.DrawIcon(API.ScreenBuffer, Bounds.X, Bounds.Y);
                    if (mnu.ItemCount > 0) API.ScreenBuffer.DrawText(">", FontManager.Arial, Color.White, Bounds.X + Bounds.Width - exW, Bounds.Y + 2);
                }
                else
                {
                    API.ScreenBuffer.DrawRectangle(NrmlTop, 0, Bounds.X - 2, Bounds.Y, Bounds.Width + 4, mnu.Height, 0, 0, NrmlTop, 0, 0, NrmlTop, 0, 0, Bitmap.OpacityOpaque);
                    API.ScreenBuffer.DrawText(mnu.Text, FontManager.Arial, (mnu.Enabled) ? Color.Black : Colors.Gray, Bounds.X + offsetX, Bounds.Y + 2);
                    mnu.DrawIcon(API.ScreenBuffer, Bounds.X, Bounds.Y);
                    if (mnu.ItemCount > 0) API.ScreenBuffer.DrawText(">", FontManager.Arial, (mnu.Enabled) ? Color.Black : Colors.Gray, Bounds.X + Bounds.Width - exW, Bounds.Y + 2);
                }


                API.ScreenBuffer.Flush(Bounds.X - 2, Bounds.Y, Bounds.Width + 4, mnu.Height);

        }

        #endregion

        #region Public Methods

        public void Add(ContextMenuItem menu)
        {
            menu.Container = this;
            _MenuItems.Add(menu);
            if (!_suspend) 
                Render(true);
        }

        public void Clear()
        {
            _MenuItems.Clear();
            if (!_suspend) 
                Render(true);
        }

        #endregion

        #region Touch

        internal bool InvokeTouchDown(point e)
        {
            ContextMenuItem mnu;

            // Check top-level first
            for (int i = 0; i < _MenuItems.Count; i++)
            {
                mnu = (ContextMenuItem)_MenuItems[i];
                if (mnu.Bounds.contains(e))
                {
                    _selMenu = mnu;
                    collapseMenus(null);
                    RenderMenuItem(mnu);

                    return true;
                }

                // Check children
                if (mnu.Expanded)
                {
                    mnu = getClickedMenu(mnu, e);
                    if (mnu != null)
                    {
                        _selMenu = mnu;

                        // In case we have any siblings open
                        if (_selMenu.Parent != null)
                        {
                            if (!collapseMenu(_selMenu.Parent, true))
                            {
                                RenderMenuItem(mnu);
                            }
                        }
                        else
                            RenderMenuItem(mnu);

                        return true;
                    }
                }

            }

            return false;
        }

        internal bool InvokeTouchUp(point e)
        {
            ContextMenuItem mnuPrev = _selMenu;

            try
            {
                // If we don't have a selected menu there's no point checking
                if (_selMenu == null)
                {
                    collapseMenus(null);
                    return false;
                }

                if (_selMenu.Bounds.contains(e))
                {
                    if (_selMenu.ItemCount > 0)
                    {
                        _selMenu.Expanded = !_selMenu.Expanded;
                        if (!_selMenu.Expanded)
                        {
                            _selMenu = null;
                            collapseMenu(mnuPrev, true);
                        }
                        else
                        {
                            _selMenu = null;
                            RenderMenuItem(mnuPrev);
                            if (mnuPrev.Parent == null)
                            {
                                drawExpandedMenu(mnuPrev, mnuPrev.Bounds.X, mnuPrev.Bounds.Y + 22);
                            }
                            else
                            {
                                rect rmnu = getExpandedMenuRect(mnuPrev.Parent, 0, 0);
                                drawExpandedMenu(mnuPrev, mnuPrev.Bounds.X + rmnu.Width - 5, mnuPrev.Bounds.Y - 2);
                            }
                        }
                    }
                    else
                    {
                        _selMenu = null;
                        API.HideContextMenu();
                        mnuPrev.invokeTap(mnuPrev);
                    }

                    return true;
                }
                else
                {
                    _selMenu = null;
                    RenderMenuItem(mnuPrev);
                    return false;
                }
            }
            catch (Exception)
            {
                // Exceptions can happed if an app unloads from a menu click
                return true;
            }
        }

        #endregion

        #region Internal Methods

        internal void ChildRequestedRender()
        {
            if (API == null)
                return;

            API.RefreshScreen();
        }

        internal void Hide()
        {
            _visible = false;

            if (API == null)
                return;

            API.RefreshScreen();
        }

        internal void Show(PyxisAPI api, int x, int y)
        {
            API = api;
            _visible = true;

            ContextMenuItem mnu = new ContextMenuItem("top level");
            for (int i = 0; i < _MenuItems.Count; i++)
                mnu.AddItem((ContextMenuItem)_MenuItems[i]);

            drawExpandedMenu(mnu, x, y, true);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Called to collapse all menu items
        /// </summary>
        private void collapseMenus(ContextMenuItem skipCollapse)
        {
            ContextMenuItem mnu;
            bool bRef = false;

            for (int i = 0; i < _MenuItems.Count; i++)
            {
                mnu = (ContextMenuItem)_MenuItems[i];
                if (mnu.Expanded && mnu != skipCollapse)
                {
                    bRef = true;
                    mnu.Expanded = false;
                    collapseMenu(mnu, false);
                }
            }

            if (bRef) 
                API.RefreshScreen();
        }

        private bool collapseMenu(ContextMenuItem mnu, bool flush)
        {
            bool bRef = false;

            for (int i = 0; i < mnu.ItemCount; i++)
            {
                if (mnu.Item(i).Expanded)
                {
                    mnu.Item(i).Expanded = false;
                    collapseMenu(mnu.Item(i), false);
                    bRef = true;
                }
            }

            if (bRef || flush)
            {
                API.RefreshScreen();
                return true;
            }

            return false;
        }

        /// <summary>
        /// Collapse all sub menu items
        /// </summary>
        /// <param name="mnu"></param>
        private void collapseSubMenus(ContextMenuItem mnu)
        {
            int i;
            ContextMenuItem submnu;
            for (i = 0; i < mnu.ItemCount; i++)
            {
                submnu = mnu.Item(i);
                submnu.Bounds = new rect(0, 0, 0, 0);

                if (submnu.Expanded)
                {
                    if (submnu.ItemCount > 0)
                    {
                        collapseSubMenus(submnu);
                    }
                    submnu.Expanded = false;
                }
                if (submnu.ItemCount > 0) collapseSubMenus(submnu);
            }
        }

        /// <summary>
        /// Used to check which (if any) of the 
        /// MenuItems were affected by the Tap.
        /// </summary>
        /// <param name="mnu"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        private ContextMenuItem getClickedMenu(ContextMenuItem mnu, point e)
        {
            int i;
            ContextMenuItem submnu;
            ContextMenuItem child;
            for (i = 0; i < mnu.ItemCount; i++)
            {
                submnu = mnu.Item(i);
                if (submnu.Enabled && submnu.Visible)
                {
                    if (submnu.Bounds.contains(e)) return submnu;
                    if (submnu.Expanded)
                    {
                        child = getClickedMenu(submnu, e);
                        if (child != null) return child;
                    }
                }
            }

            return null;
        }

        private rect getExpandedMenuRect(ContextMenuItem mnu, int x, int y)
        {
            rect Bounds = new rect();

            ContextMenuItem m;
            int i;
            int w = 0;  // Total menu width
            int h = 4;  // Total menu height

            int exW, exH;
            FontManager.Arial.ComputeExtent(">", out exW, out exH);

            // Calculate menu size
            for (i = 0; i < mnu.ItemCount; i++)
            {
                m = mnu.Item(i);
                if (m.Visible)
                {
                    if (m.Width > w) w = m.Width;
                    h += m.Height;
                }
            }
            w += 8;

            // Verify location
            // We don't want menus going off the screen
            if (x + w > AppearanceManager.ScreenWidth)
            {
                x = AppearanceManager.ScreenWidth - w;
                if (x < 0) x = 0;
            }
            if (y + h > AppearanceManager.ScreenHeight)
            {
                y = AppearanceManager.ScreenHeight - h;
                if (y < 21) y = 21;
            }

            Bounds.X = x;
            Bounds.Y = y;
            Bounds.Width = w;
            Bounds.Height = h;

            return Bounds;
        }

        /// <summary>
        /// Check to see if any menus in the group has an icon
        /// </summary>
        /// <param name="mnu">Parent menu</param>
        /// <returns>True if an icon is found</returns>
        private bool menuHasIcons(ContextMenuItem mnu)
        {
            for (int i = 0; i < mnu.ItemCount; i++)
            {
                if (mnu.Item(i).icon != null && mnu.Item(i).Visible) return true;
            }
            return false;
        }

        #endregion

    }
}
